// Copyright (C) 2015  misaka15842

// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 2 of the License, or (at your option)
// any later version.

// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
// more details.

// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

/**
 * @file mkpkg.cpp
 * @author misaka15842
 * @date 2015.3.23
 *
 * @functions:
 *     packfiles
 *     write_package
 *     main
 *
 * @TODO: simplify implementation , more better expansibility , compress
 *
 * This is exercise of C++ Primer
 */

#include <iostream>
#include <fstream>
#include <stdexcept>
#include <vector>
#include <string>
#include <cstring>

#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>

size_t packfiles(std::string path, std::vector<std::string>& ret) throw(std::logic_error)
{
    DIR* ds = opendir(path.c_str());

    if(ds == nullptr)
    {
        throw(std::logic_error(strerror(errno)));
    }

    dirent* d = readdir(ds);

    if(d == nullptr)
    {
        throw(std::logic_error(strerror(errno)));
    }

    while(d != nullptr)
    {
        if(strcmp(d->d_name, ".") != 0 &&
                strcmp(d->d_name, "..") != 0)
        {
            if(d->d_type == DT_DIR)
            {
                try
                {
                    packfiles(path + "/" + d->d_name, ret);
                }
                catch(std::logic_error& e)
                {
                    closedir(ds);
                    throw(std::logic_error(e.what() + std::string(d->d_name)));
                }
            }
            else
            {
                ret.push_back(std::string(path + "/" + d->d_name));
            }
        }

        d = readdir(ds);

        if(d == nullptr)
            if(errno == EBADF)
            {
                perror("");
            }
    }

    closedir(ds);

    return ret.size();
}

size_t write_package(std::vector<std::string>& v)
{
    // write two files,idx.dat is index of package.bin

    std::ofstream idx("idx.dat", std::ofstream::binary);
    std::cerr << idx.is_open() << std::endl;

    if(!idx)
    {
        throw(std::runtime_error("open idx.dat failed."));
    }

    std::ifstream rf;

    // copy all file to package
    for(auto i : v)
    {
        rf.open(i, std::ifstream::binary);

        if(!rf)
        {
            throw(std::runtime_error("can not open " + i));
        }

        rf.seekg(0, std::ios_base::end);
        size_t sz = rf.tellg();
        std::string fname = i;
        fname.append("\"");
        fname.insert(fname.begin(), '\"');
        idx.write(fname.c_str(), fname.size());
        idx.write((char*)&sz, sizeof(sz));
        rf.close();
    }

    std::ofstream package("package.bin", std::ofstream::binary);

    if(!package.is_open())
    {
        throw(std::runtime_error("can not open package.bin"));
    }

    for(auto i : v)
    {
        rf.open(i, std::ifstream::binary);

        if(!rf)
        {
            throw(std::runtime_error("can not open " + i));
        }

        // empty file check
        rf.seekg(0, std::ifstream::end);

        if(rf.tellg() == 0)
        {
            std::cerr << "empty file " << i << std::endl;
            rf.close();
            continue;
        }

        rf.seekg(0, std::ifstream::beg);

        if(!(package << rf.rdbuf()))
        {
            package.close();
            rf.close();
            throw(std::runtime_error("write error with " + i));
        }

        rf.close();
    }

    std::cerr << "write " << package.tellp() << " byte" << std::endl;

    size_t ret = idx.tellp();
    idx.close();
    package.close();
    return ret;
}

int main(void)
{
    std::vector<std::string> v;

    try
    {
        std::cout << "read " << packfiles("linux", v) << "files." << std::endl;
    }
    catch(std::runtime_error& e)
    {
        std::cerr << e.what() << std::endl;
        abort();
    }

    write_package(v);

    return 0;
}
